/**
 * 性能优化指南:  尽量避免圆角(radius), 裁剪(overflowHidden), 点坐标越少越好(pointArr), 减少元素draw方法内的计算量,减少ctx.属性的设置, 方法的调用
 */
import { CoordinateSystem, FontFamily, Events, ClassName, AdsorbType } from "./Constants";
import Feature from "./features/Feature";
import Line from "./features/basic-shape/Line";
import Rect from "./features/basic-shape/Rect";
import AdsorbPnt from "./features/function-shape/func-pnts/AdsorbPnt";
import { IPoint, IPixelPos, IProps, IRelativePos, Listeners, IGridPos, IAdsorbPos } from "./Interface";
import Stack from "./Stack";
import { Utils } from "./utils";
import Shortcuts from "./Shortcuts";
import Img from "./features/basic-shape/Img";
import Text from "./features/basic-shape/Text";
import Bbox from "./features/function-shape/Bbox";
import Circle from "./features/basic-shape/Circle";
import SelectArea from "./features/function-shape/SelectArea";
import Group from "./features/function-shape/Group";
import Link from "./features/basic-shape/link/Link";
import Pnt from "./features/function-shape/Pnt";
import Pen from "./features/basic-shape/Pen";
import Video from "./features/basic-shape/Video";
import CtrlPnt from "./features/function-shape/ctrl-pnts/CtrlPnt";
import Cursor from "./features/function-shape/Cursor";
import BCtrlPnt from "./features/function-shape/ctrl-pnts/BCtrlPnt";

const fontMap = new Map([
    [FontFamily.ZONGYI, "/fonts/方正综艺简体.ttf"],
    [FontFamily.SHISHANG, "/fonts/时尚中黑简体.ttf"],
    [FontFamily.YOUSHE, "/fonts/优设标题黑.ttf"],
])

let timerOfDraw = 0;  // 画布绘制的定时器
let timerOfFriction = 0; // 画布拖拽摩擦力停止的定时器

class GridSystem {

    static Gls: GridSystem;
    static Stack: Stack;
    static Bbox: Bbox;
    static Shortcuts: Shortcuts;
    static MultipleSelect: Group;
    static isMobile = Utils.isMobileDevice();  // 是否移动端
    static AdsorbPos: IAdsorbPos | null = null;  // 吸附线

    static longPressDuration = 600;
    static dbclickDuration = 250;
    static scaleShowMinSize = 10;
    static dragTransitionMinDist = 20;

    // 提供的事件
    private _listeners: Listeners = {};
    [key: string]: any;
    className: string = ClassName.GRIDSYSTEM;

    domElement: HTMLCanvasElement;
    ctx: CanvasRenderingContext2D;

    scale: number = 13;
    scaleSpeed = .8;  // 缩放速度
    minScaleSize = 1; // 最小缩小比例
    maxScaleSize = 60; // 最大放大比例
    _background: string = 'rgba(0,0,0, 0)' // 画布的背景色  
    get background() { return this._background }
    set background(val) {
        this.domElement.style.background = val;
        this._background = val;
    }

    _width: number = 100
    get width() { return this._width }
    set width(val) {
        this.ctx.canvas.width = val;
        this._width = val;
    }
    _height: number = 100
    get height() { return this._height }
    set height(val) {
        this.ctx.canvas.height = val;
        this._height = val;
    }

    mousePos: IPixelPos = { x: 0, y: 0 }  // 当前鼠标位置
    pageSlicePos: IPixelPos = { x: 350, y: 150 };
    extent: [number, number, number, number] = [Infinity, Infinity, Infinity, Infinity]  // 限制画布拖拽范围: 上右下左,顺时针  测试 750, 800, 750, 800;

    focusNode: Feature | null | undefined;  // 获取焦点的元素, 如果是null ，那就是画布
    hoverNode: Feature | null | undefined;
    features: Feature[] = [];  // 所有元素的集合

    dragSensitivity: number = 1;   // 拖拽时候的灵敏度, 建议 0 ~ 3
    friction = .93;  // 摩擦力

    cbScale: boolean = true; // 画布是否可调节缩放
    cbDragBackground: boolean = true;  // 画布是否可被拖拽
    cbOperable: boolean = true;  // 画布是否可操作,包括是否可选中元素,是否可拖拽
    cbAdsorption: boolean = false;  // 元素拖拽是否启用吸附
    cbDrawMiniFeature: boolean = true; // 是否渲染太小的元素，因为画布缩放的原因, 提升渲染效率
    cbDrawOutScreen: boolean = true;  // 元素在屏幕外时是否绘制， 因为画布拖拽, 提升渲染效率
    cbDrawAdsorbLine = true;
    cbContentTranslateOutScreen = true;  // 所有元素组成的可视范围是否能移出到屏幕外, 如果设为false 那你可能需要设置不可缩放
    _cbTransformFeature = true; // 元素是否可被形变
    get cbTransformFeature() { return this._cbTransformFeature }
    set cbTransformFeature(val) {
        if (!val) this.enableBbox(null)
        this._cbTransformFeature = val;
    }

    _tests: IPixelPos[] = []
    _firstPageSlicePos: IPixelPos = Object.freeze({ x: this.pageSlicePos.x, y: this.pageSlicePos.y });  // 首次渲染时候的pagePos

    constructor(canvasDom: HTMLCanvasElement = document.createElement("canvas"), isMain = true) {
        // 当前 canvas 的 0 0 坐标，我们设置 canvas 左上角顶点为 0 0，向右👉和向下👇是 X Y 轴正方向，0，0 为 pageSlicePos 初始值
        if (isMain) Feature.Gls = this;
        this.domElement = canvasDom;
        this.domElement.style.imageRendering = 'optimizeSpeed'
        this.domElement.style.userSelect = 'none'
        this.ctx = this.domElement.getContext('2d') || new CanvasRenderingContext2D();
        this.pageSlicePos = {
            x: this.ctx.canvas.width / 2,
            y: this.ctx.canvas.height / 2,
        }
    }

    draw(loop = true, fn?: Function) {
        timerOfDraw && cancelAnimationFrame(timerOfDraw)
        // console.time();
        // console.log("clear");
        this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
        // this.ctx.save()
        // this.ctx.rotate(30 * Math.PI/180)
        this.drawFeatures();
        this.drawAdsorbLine(); // 绘制对齐线
        this.ctx.fillStyle = "red"
        this._tests.forEach(p => {
            this.ctx.fillRect(p.x, p.y, 5, 5);
        })
        fn && fn();
        // this.ctx.restore()
        Cursor.instance?.draw(this.ctx);
        this.dispatch(new CustomEvent(Events.DRAW, { detail: { target: this } }))
        // console.timeEnd();
        if (loop) {  // 是否循环渲染
            // timerOfDraw = setInterval(() => { this.draw(loop, fn) })
            timerOfDraw = window.requestAnimationFrame(() => this.draw(loop, fn))
        }
    };

    // --------------------以下是私有的方法----------------------------
    initEventListener() {
        document.body.style.overflow = 'hidden';
        GridSystem.Shortcuts = new Shortcuts();
        if (GridSystem.isMobile) {
            this.domElement.addEventListener(Events.TOUCHSTART, this.mouseDown.bind(this), false);
        } else {
            this.domElement.addEventListener(Events.MOUSE_MOVE, this.mouseMove.bind(this));
            this.domElement.addEventListener(Events.MOUSE_DOWN, this.mouseDown.bind(this));
            this.domElement.addEventListener(Events.CONTEXTMENU, (e) => { e.preventDefault(); });
            this.domElement.addEventListener("drop", e => this.dropToFeature(e));
            document.ondragover = function (e) { e.preventDefault(); };  // 阻止默认应为,不然浏览器会打开新的标签去预览
            document.ondrop = function (e) { e.preventDefault(); };
            GridSystem.Shortcuts.addEvent(["ctrl", "v"], this.clipboardToFeature.bind(this))
            GridSystem.Shortcuts.addEvent(["ctrl", "u"], () => {
                const feature = this.getFocusNode();
                if (feature instanceof SelectArea) {
                    const group = new Group(...feature.getBasicChilds());
                    this.addFeature(group)
                    this.toMinIndex(group)
                }
            })
            GridSystem.Shortcuts.addEvent("esc", () => {
                const sa = this.features.find(f => f instanceof SelectArea) as SelectArea;
                this.removeFeature(sa)
            })
            GridSystem.Shortcuts.addEvent("del", () => {
                const focusNode = this.getFocusNode();
                if (focusNode) {
                    if (focusNode instanceof Text && focusNode.editable) return
                    focusNode.isDelWithChild = true;
                    this.removeFeature(focusNode);
                    this.enableBbox(null)
                }
            })
        }
        this.domElement.addEventListener(Events.ZOOM, this.mouseWheel.bind(this));
        document.addEventListener(Events.DBLCLICK, this.dbClick.bind(this), true);
        // this.on(Events.DBLCLICK, (e: any) => {  // 移动端无效
        //     this.setCurrentPos(e.detail.event);
        //     this.drawFeatures()
        //     if (!this.features.find(f => f.isPointIn)) {  // 点击空白处生成文本
        //         const pos = this.getRelativePos(Utils.getMousePos(this.domElement, e.detail.event));
        //         if (GridSystem.isMobile) {
        //             var txt = prompt("请输入文字", "");
        //             const text = new Text(txt?.toString(), pos.x, pos.y);
        //             this.addFeature(text);
        //         } else {  // pc
        //             const text = new Text("", pos.x, pos.y);
        //             this.addFeature(text);
        //             text.onEdit(e, 0)
        //         }
        //     }
        // });
    }

    dbClick(e: any) {
        console.log(11);
        this.setCurrentPos(e);
        this.drawFeatures();
        const hoverNodes = [...this.features].reverse().filter(f => f.isPointIn);  // 寻找hoverNode元素
        this.dispatch(new CustomEvent(Events.DBLCLICK, { detail: e }))
        hoverNodes.forEach(f => f.dispatch(new CustomEvent(Events.DBLCLICK, { detail: e })))
    }

    setCurrentPos(e: any) {
        const pos = Utils.getMousePos(this.domElement, e);
        this.mousePos.x = pos.x;
        this.mousePos.y = pos.y;
    }

    private mouseMove(e: any) {
        const lastHoverNode = this.hoverNode;
        this.setCurrentPos(e);
        this.hoverNode = [...this.features].reverse().find(f => f.isPointIn);   // 扩展运算符防止改变原数组
        this.dispatch(new CustomEvent(Events.MOUSE_MOVE, { detail: e }))
        if (this.hoverNode && !lastHoverNode) {
            this.domElement.style.cursor = "pointer";
            this.hoverNode.dispatch(new CustomEvent(Events.MOUSE_ENTER, { detail: Feature.Gls.mousePos }))
        } else if (this.hoverNode) {
            this.hoverNode.dispatch(new CustomEvent(Events.MOUSE_MOVE, { detail: Feature.Gls.mousePos }))
        } else if (!this.hoverNode && lastHoverNode) {
            this.domElement.style.cursor = "default";
            lastHoverNode.dispatch(new CustomEvent(Events.MOUSE_LEAVE, { detail: Feature.Gls.mousePos }))
        }
    }

    getFeatures(features = this.features, result: Feature[] = []) {
        features.forEach(feature => {
            result.push(feature); // 将当前 feature 添加到结果中  
            if (feature.children && feature.children.length > 0) {
                // 如果存在 children，递归调用此函数  
                this.getFeatures(feature.children, result);
            }
        });
        return result;
    }

    private mouseDown(de: any) {
        de.preventDefault();
        if (!GridSystem.isMobile) this.dispatch(new CustomEvent(Events.MOUSE_DOWN, { detail: de }))
        if (GridSystem.isMobile) this.dispatch(new CustomEvent(Events.TOUCHSTART, { detail: de }))
        this.setCurrentPos(de);
        this.drawFeatures()
        if (GridSystem.isMobile) this.pinch2zoom(de)
        if (GridSystem.isMobile) this.longPress(de)
        if (GridSystem.isMobile && de.touches.length != 1) return;
        GridSystem.AdsorbPos = null;
        const features = this.getFeatures(this.features);
        const lastFocusNode = this.getFocusNode();  // 上一次的焦点元素
        const velocity = { x: 0, y: 0 }; // 速度分量
        const lastMove = { x: 0, y: 0 }; // 速度分量
        let leftMoveFlag = false, rightMoveFlag = false;  // 左右键是否按住拖动标记
        features.forEach(f => f.isFocused = false);  // 清除所有元素焦点状态
        const hoverNodes = [...features].reverse().filter(f => f.isPointIn);  // 寻找hoverNode元素
        let basicFocusNode: Feature | undefined;
        let mousemove = (e: any) => { };
        const { x: downX, y: downY } = Utils.getMousePos(this.domElement, de);
        if (this.cbOperable) {
            // if (de.buttons == Events.LEFT_BTN) {  // 左键点击
            let focusNode = this.focusNode = hoverNodes[0];  // 寻找鼠标悬浮元素
            basicFocusNode = this.getFocusNode(focusNode);  // 获取focusNode中的基础元素
            if (lastFocusNode && (basicFocusNode !== lastFocusNode)) { lastFocusNode.dispatch(new CustomEvent(Events.BLUR, { detail: de })) };
            if (!hoverNodes.find(f => f instanceof Bbox) && !(focusNode instanceof CtrlPnt) && !(focusNode instanceof BCtrlPnt)) {  // 点击了就加控制点,没点击就去除所有控制点
                const bbox = this.enableBbox(focusNode);
                bbox && (focusNode = bbox);
            };
            if (Shortcuts.isCtrlKey && basicFocusNode) {  // ctrl 多选
                const sa = SelectArea.getInstance(false);
                sa.addMember(basicFocusNode)
            } else if (!hoverNodes.find(f => f instanceof SelectArea) && !(focusNode instanceof CtrlPnt) && !(focusNode instanceof BCtrlPnt)) { // 如果有区域选择,那么选择其他元素或者点击空白就清除SelectArea
                this.enableSelectArea(false)
            }
            if (hoverNodes.length > 0) {  // right_click
                let { x: dx, y: dy } = this.getRelativePos({ x: downX, y: downY }, focusNode.isFixedPos)
                lastMove.x = dx;
                lastMove.y = dy;
                if (focusNode.cbDrag) {
                    focusNode.dispatch(new CustomEvent(Events.DRAG_START, { detail: de }));
                    if (!focusNode.isFocused) {
                        focusNode.dispatch(new CustomEvent(Events.FOCUS, { detail: de }));
                        focusNode.isFocused = true;
                    }
                    mousemove = (e: any) => {
                        e.preventDefault();
                        if (focusNode) {
                            const { x: moveX, y: moveY } = Utils.getMousePos(this.domElement, e);
                            let { x: mx, y: my } = this.getRelativePos({ x: moveX, y: moveY }, focusNode.isFixedPos)
                            focusNode.translate(mx - lastMove.x, my - lastMove.y); // 移动元素
                            if (this.cbAdsorption && focusNode.adsorbTypes.length > 0 && !focusNode.isFixedPos && (lastMove.x != mx && lastMove.y != my)) {  // 是否开启边缘吸附, 并且是在移动
                                const { x: offsetX, y: offsetY, adsorbPos } = this.getAdsorbOffsetDist(focusNode, {  // 不要getFocusNode,否则无法控制点无效
                                    gridCompute: focusNode.adsorbTypes.includes(AdsorbType.GRID),
                                    featureCompute: focusNode.adsorbTypes.includes(AdsorbType.FEATURE),
                                    pointCompute: focusNode.adsorbTypes.includes(AdsorbType.POINT),
                                    onlyCenter: focusNode.isOnlyCenterAdsorb,
                                });
                                GridSystem.AdsorbPos = adsorbPos;
                                focusNode.translate(offsetX, offsetY)
                                mx += offsetX;
                                my += offsetY;
                            }
                            leftMoveFlag = true;
                            lastMove.x = mx;
                            lastMove.y = my;
                            focusNode.dispatch(new CustomEvent(Events.DRAG, { detail: e }));
                        }
                    }
                }
            } else {
                // if (de.buttons === Events.RIGHT_BTN) {  // 判断右击
                this.enableBbox(null);
                if (this.cbDragBackground) {  // 判断是否右键拖拽画布
                    lastMove.x = downX;
                    lastMove.y = downY;
                    this.domElement.style.cursor = "grabbing"
                    mousemove = (e: any) => {
                        e.preventDefault();
                        const { x: moveX, y: moveY, isoutX, isoutY } = Utils.getMousePos(this.domElement, e);
                        // console.log(moveY, "moveY");
                        if (lastMove.x != undefined && lastMove.y != undefined) {
                            this.translate((moveX - lastMove.x) * this.dragSensitivity, (moveY - lastMove.y) * this.dragSensitivity, { isoutX, isoutY })
                            velocity.x = moveX - lastMove.x; // 计算dx
                            velocity.y = moveY - lastMove.y; // 计算dy
                        }
                        lastMove.x = moveX;
                        lastMove.y = moveY;
                        rightMoveFlag = true;
                        this.dispatch(new CustomEvent(Events.DRAG, { detail: e }));
                    }
                }
                if (de.buttons === Events.RIGHT_BTN) {  // 判断右击
                    this.dispatch(new CustomEvent(Events.RIGHT_CLICK, { detail: de }))
                    hoverNodes.forEach(f => {
                        f.dispatch(new CustomEvent(Events.RIGHT_CLICK, { detail: de }))
                    })
                }
            }
        }

        // 拖拽松开时
        const mouseup = (e: any) => {
            GridSystem.AdsorbPos = null;
            this.domElement.style.cursor = "auto";
            this.cbOperable = true;
            if (!GridSystem.isMobile) this.dispatch(new CustomEvent(Events.MOUSE_UP, { detail: Object.assign(e, rightMoveFlag, leftMoveFlag) }))
            if (GridSystem.isMobile) this.dispatch(new CustomEvent(Events.TOUCHEND, { detail: Object.assign(e, rightMoveFlag, leftMoveFlag) }))
            hoverNodes.forEach(f => {
                f.dispatch(new CustomEvent(Events.MOUSE_UP, { detail: Object.assign(e, rightMoveFlag, leftMoveFlag) }))
                if (GridSystem.isMobile) f.dispatch(new CustomEvent(Events.TOUCHEND, { detail: Object.assign(e, rightMoveFlag, leftMoveFlag) }))
            })
            if (leftMoveFlag) { // 鼠标抬起后,记录一下
                this.focusNode?.dispatch(new CustomEvent(Events.DRAG_END, { detail: e }))
                this.dispatch(new CustomEvent(Events.CHANGED, { detail: e }))
            }
            if (!GridSystem.isMobile) document.removeEventListener(Events.MOUSE_MOVE, mousemove)
            if (!GridSystem.isMobile) document.removeEventListener(Events.MOUSE_UP, mouseup);
            if (GridSystem.isMobile) document.removeEventListener(Events.TOUCHMOVE, mousemove)
            if (GridSystem.isMobile) document.removeEventListener(Events.TOUCHEND, mouseup);
            // 摩擦力过渡停止
            this.stopByfriction(e, velocity)
        }

        // // 发送双击事件
        // if (new Date().getTime() - lastClickTime < GridSystem.dbclickDuration) {
        //     this.dispatch(new CustomEvent(Events.DBLCLICK, { detail: de }))
        //     hoverNodes.forEach(f => f.dispatch(new CustomEvent(Events.DBLCLICK, { detail: de })))
        // }
        // lastClickTime = new Date().getTime();

        // 发送单击事件
        hoverNodes.forEach(f => {
            if (!GridSystem.isMobile) f.dispatch(new CustomEvent(Events.MOUSE_DOWN, { detail: de }))
            if (GridSystem.isMobile) f.dispatch(new CustomEvent(Events.TOUCHSTART, { detail: de }))
        })

        // 移出鼠标事件监听
        if (!GridSystem.isMobile) document.addEventListener(Events.MOUSE_UP, mouseup)
        if (!GridSystem.isMobile) document.addEventListener(Events.MOUSE_MOVE, mousemove)
        if (GridSystem.isMobile) document.addEventListener(Events.TOUCHEND, mouseup)
        if (GridSystem.isMobile) document.addEventListener(Events.TOUCHMOVE, mousemove, { passive: false })
    }

    private longPress(e: any) {
        let timeOutEvent = setTimeout(() => {
            timeOutEvent = 0;
            this.dispatch(new CustomEvent(Events.CONTEXTMENU, { detail: e }))
        }, GridSystem.longPressDuration); // 设置长按时间阈值（毫秒）  

        const move2clear = () => {
            clearTimeout(timeOutEvent);
            timeOutEvent = 0;
        }
        const up2clear = () => {
            clearTimeout(timeOutEvent);
            this.domElement.removeEventListener(Events.TOUCHEND, up2clear);
            this.domElement.removeEventListener(Events.TOUCHMOVE, move2clear);
        }
        // 手指移动时清除定时器  
        // 手指抬起时检查是否触发长按事件  
        this.domElement.addEventListener(Events.TOUCHMOVE, move2clear, { passive: false });
        this.domElement.addEventListener(Events.TOUCHEND, up2clear);
    }

    private pinch2zoom(e: any) {
        if (e.touches.length === 2) { // 检查是否有两个触点
            const pinchHandler = (pinchEvent: any) => {   // 双指缩放
                pinchEvent.preventDefault();
                const touch1 = pinchEvent.touches[0];
                const touch2 = pinchEvent.touches[1];
                const endDistance = Math.hypot(touch2.pageX - touch1.pageX, touch2.pageY - touch1.pageY);
                const newScale = endDistance / startDistance;
                this.zoomTo(this.scale * newScale, Utils.getMidOfTwoPnts({ x: touch1.pageX, y: touch1.pageY }, { x: touch2.pageX, y: touch2.pageY }))
                startDistance = endDistance;
            }
            const touch1 = e.touches[0];
            const touch2 = e.touches[1];
            let startDistance = Math.hypot(touch2.pageX - touch1.pageX, touch2.pageY - touch1.pageY);  // 求平方根,即两点之间距离
            const clear = () => {
                this.domElement.removeEventListener(Events.TOUCHEND, clear);
                this.domElement.removeEventListener(Events.TOUCHMOVE, pinchHandler);
            }
            this.domElement.addEventListener(Events.TOUCHMOVE, pinchHandler, { passive: false });
            this.domElement.addEventListener(Events.TOUCHEND, clear);
        }
    }

    private stopByfriction(e: any, velocity: IPoint, friction = this.friction, stopDist = .1) {
        const { isoutX, isoutY } = Utils.getMousePos(this.domElement, e);
        if (friction > 0 && (Math.abs(velocity.x) > GridSystem.dragTransitionMinDist || Math.abs(velocity.y) > GridSystem.dragTransitionMinDist)) {  // 有设置摩擦力,and 速度分量要到一定程度才缓动
            cancelAnimationFrame(timerOfFriction);
            const that = this;
            (function animate() {
                that.translate(velocity.x * that.dragSensitivity, velocity.y * that.dragSensitivity, { isoutX, isoutY })
                velocity.x *= friction;
                velocity.y *= friction;
                timerOfFriction = requestAnimationFrame(animate);
                if (Math.abs(velocity.x) < stopDist && Math.abs(velocity.y) < stopDist) {
                    cancelAnimationFrame(timerOfFriction);
                }
            })()
        }
    }

    /**
     * 网格吸附，获取吸附边缘的偏离值
     * @param feature 
     * @param options 
     * @returns 
     */
    private getAdsorbOffsetDist(feature: Feature, options: {
        gridCompute?: boolean,
        featureCompute?: boolean,
        onlyCenter?: boolean,
        pointCompute?: boolean,
        keepMargin?: boolean,
    } = {
            gridCompute: false, featureCompute: false, onlyCenter: false, pointCompute: false, keepMargin: false
        }) {
        const margin = options.keepMargin ? feature.margin : 0;
        const gridSize = CoordinateSystem.GRID_SIZE;
        let offsetX = 0, offsetY = 0, adsorbPos: IAdsorbPos = { x: 0, y: 0 };
        const [leftX, rightX, topY, bottomY] = Feature.getRectWrapExtent(feature.pointArr, margin);
        const { x: centerX, y: centerY } = Feature.getCenterPos(feature.pointArr);

        // 吸附的约束，灵敏度
        let min = gridSize * .2;
        let max = gridSize * .8;

        function getDeviation(num: number): number {   // 附近可吸附的位置
            const gridSize = CoordinateSystem.GRID_SIZE;
            return (num / gridSize) % gridSize;
        }

        if (options.gridCompute) {
            //  ------------- 水平对齐
            if (!options.onlyCenter) {
                // 以元素左边为基准
                const offsetLeftX = getDeviation(leftX);
                if (offsetX == 0 && (offsetLeftX > 0 && offsetLeftX < min) || (offsetLeftX < 0 && offsetLeftX > -min)) {
                    offsetX = -leftX % gridSize;
                    adsorbPos.x = this.getPixelPos({ x: leftX + offsetX, y: 0 }).x
                }
                if (offsetX == 0 && (offsetLeftX > max && offsetLeftX < gridSize) || (offsetLeftX > -gridSize && offsetLeftX < -max)) {
                    offsetX = gridSize * (offsetLeftX > 0 ? 1 : -1) - leftX % gridSize;
                    adsorbPos.x = this.getPixelPos({ x: leftX + offsetX, y: 0 }).x
                }
                // 以元素右边为基准
                const offsetRightX = getDeviation(rightX);
                if (offsetX == 0 && (offsetRightX > 0 && offsetRightX < min) || (offsetRightX < 0 && offsetRightX > -min)) {
                    offsetX = -rightX % gridSize;
                    adsorbPos.x = this.getPixelPos({ x: rightX + offsetX, y: 0 }).x
                }
                if (offsetX == 0 && (offsetRightX > max && offsetRightX < gridSize) || (offsetRightX > -gridSize && offsetRightX < -max)) {
                    offsetX = gridSize * (offsetRightX > 0 ? 1 : -1) - rightX % gridSize;
                    adsorbPos.x = this.getPixelPos({ x: rightX + offsetX, y: 0 }).x
                }
            }
            // 以中心为基准
            const offsetCenterX = getDeviation(centerX);
            if (offsetX == 0 && (offsetCenterX > 0 && offsetCenterX < min) || (offsetCenterX < 0 && offsetCenterX > -min)) {
                offsetX = -centerX % gridSize;
                adsorbPos.x = this.getPixelPos({ x: centerX + offsetX, y: 0 }).x
            }
            if (offsetX == 0 && (offsetCenterX > max && offsetCenterX < gridSize) || (offsetCenterX > -gridSize && offsetCenterX < -max)) {
                offsetX = gridSize * (offsetCenterX > 0 ? 1 : -1) - centerX % gridSize;
                adsorbPos.x = this.getPixelPos({ x: centerX + offsetX, y: 0 }).x
            }

            //  ------------- 垂直对齐
            // 以元素上边为基准
            if (!options.onlyCenter) {
                const offsetTopY = getDeviation(topY);
                if (offsetY == 0 && (offsetTopY > 0 && offsetTopY < min) || (offsetTopY < 0 && offsetTopY > -min)) {
                    offsetY = -topY % gridSize;
                    adsorbPos.y = this.getPixelPos({ y: topY + offsetY, x: 0 }).y
                }
                if (offsetY == 0 && (offsetTopY > max && offsetTopY < gridSize) || (offsetTopY > -gridSize && offsetTopY < -max)) {
                    offsetY = gridSize * (offsetTopY > 0 ? 1 : -1) - topY % gridSize;
                    adsorbPos.y = this.getPixelPos({ y: topY + offsetY, x: 0 }).y
                }
                // 以元素下边为基准
                const offsetBottomY = getDeviation(bottomY);
                if (offsetY == 0 && (offsetBottomY > 0 && offsetBottomY < min) || (offsetBottomY < 0 && offsetBottomY > -min)) {
                    offsetY = -bottomY % gridSize;
                    adsorbPos.y = this.getPixelPos({ y: bottomY + offsetY, x: 0 }).y
                }
                if (offsetY == 0 && (offsetBottomY > max && offsetBottomY < gridSize) || (offsetBottomY > -gridSize && offsetBottomY < -max)) {
                    offsetY = gridSize * (offsetBottomY > 0 ? 1 : -1) - bottomY % gridSize;
                    adsorbPos.y = this.getPixelPos({ y: bottomY + offsetY, x: 0 }).y
                }
            }
            // 以中心为基准
            const offsetCenterY = getDeviation(centerY);
            if (offsetY == 0 && (offsetCenterY > 0 && offsetCenterY < min) || (offsetCenterY < 0 && offsetCenterY > -min)) {
                offsetY = -centerY % gridSize;
                adsorbPos.y = this.getPixelPos({ y: centerY + offsetY, x: 0 }).y
            }
            if (offsetY == 0 && (offsetCenterY > max && offsetCenterY < gridSize) || (offsetCenterY > -gridSize && offsetCenterY < -max)) {
                offsetY = gridSize * (offsetCenterY > 0 ? 1 : -1) - centerY % gridSize;
                adsorbPos.y = this.getPixelPos({ y: centerY + offsetY, x: 0 }).y
            }
        }
        if (options.featureCompute) {
            const min = gridSize;
            // 元素间对其 (不需要跟自己对齐, 不需要跟非基础元素对齐,不需要跟实际focusNode对齐, 不跟自己的子元素对齐)
            // console.log(focusNode, feature, "123");
            // feature.children.find(cf => f === cf || f.children.find());
            const features = this.features.filter(f => f != feature && f != this.getFocusNode() && Utils.isBasicFeature(f, true, false) && !feature.children.find(cf => f === cf));
            for (let index = 0; index < features.length; index++) {
                const f = features[index];
                const [left, right, top, bottom] = Feature.getRectWrapExtent(f.pointArr, margin);
                if (offsetX == 0) {
                    const hxs = [left, right, (left + right) / 2]
                    for (let index = 0; index < hxs.length; index++) {
                        const hx = hxs[index];
                        if (Math.abs(centerX - hx) < min) {
                            if (leftX > hx) {
                                offsetX = -(centerX - hx);
                            } else {
                                offsetX = (hx - centerX);
                            }
                            adsorbPos.x = offsetX && this.getPixelPos({ x: centerX + offsetX, y: 0 }).x
                            break;
                        }
                        if (!options.onlyCenter) {
                            if (Math.abs(leftX - hx) < min) {
                                if (leftX > hx) {
                                    offsetX = -(leftX - hx);
                                } else {
                                    offsetX = (hx - leftX);
                                }
                                adsorbPos.x = offsetX && this.getPixelPos({ x: leftX + offsetX, y: 0 }).x
                                break;
                            }
                            if (Math.abs(rightX - hx) < min) {
                                if (leftX > hx) {
                                    offsetX = -(rightX - hx);
                                } else {
                                    offsetX = (hx - rightX);
                                }
                                adsorbPos.x = offsetX && this.getPixelPos({ x: rightX + offsetX, y: 0 }).x
                                break;
                            }
                        }
                        adsorbPos.y1 = this.getPixelPos({ x: 0, y: (topY + bottomY) / 2 }).y;
                        adsorbPos.y2 = this.getPixelPos({ x: 0, y: (top + bottom) / 2 }).y;
                    }
                }
                if (offsetY == 0) {
                    const vys = [top, bottom, (top + bottom) / 2]
                    for (let index = 0; index < vys.length; index++) {
                        const vy = vys[index];
                        if (Math.abs(centerY - vy) < min) {
                            if (topY > vy) {
                                offsetY = -(centerY - vy);
                            } else {
                                offsetY = (vy - centerY);
                            }
                            adsorbPos.y = offsetY && this.getPixelPos({ y: centerY + offsetY, x: 0 }).y
                            break;
                        }
                        if (!options.onlyCenter) {
                            if (Math.abs(topY - vy) < min) {
                                if (topY > vy) {
                                    offsetY = -(topY - vy);
                                } else {
                                    offsetY = (vy - topY);
                                }
                                adsorbPos.y = offsetY && this.getPixelPos({ y: topY + offsetY, x: 0 }).y
                                break;
                            }
                            if (Math.abs(bottomY - vy) < min) {
                                if (bottomY > vy) {
                                    offsetY = -(bottomY - vy);
                                } else {
                                    offsetY = (vy - bottomY);
                                }
                                adsorbPos.y = offsetY && this.getPixelPos({ y: bottomY + offsetY, x: 0 }).y
                                break;
                            }
                        }
                        adsorbPos.x1 = this.getPixelPos({ x: (leftX + rightX) / 2, y: 0 }).x;
                        adsorbPos.x2 = this.getPixelPos({ x: (left + right) / 2, y: 0 }).x;
                    }
                }
            }
        }
        if (options.pointCompute) {  // 元素中心点跟Pnt类型元素对其
            const minD = 5;
            if (offsetX == 0 || offsetY == 0) {
                const center = { x: centerX, y: centerY }
                const points = this.features.filter(f => f instanceof Pnt).map(f => Feature.getCenterPos(f.pointArr));
                const nearP = Utils.getNearestPoint(center, points) as IRelativePos;
                if (nearP) {
                    const d = Utils.getLenOfTwoPnts(nearP, center);
                    if (d < minD) {
                        offsetX = nearP.x - center.x;
                        offsetY = nearP.y - center.y;
                    }
                }
            }
        }
        return { x: offsetX, y: offsetY, adsorbPos };
    }

    // 绘制对齐线
    private drawAdsorbLine() {
        const adsorbPos = GridSystem.AdsorbPos;
        if (adsorbPos && this.cbDrawAdsorbLine && this.cbAdsorption) {
            const ctx = this.ctx;
            ctx.save()
            ctx.beginPath();
            if (adsorbPos.x && adsorbPos.x != 0) {
                ctx.moveTo(adsorbPos.x, adsorbPos.y1 || 0)
                ctx.lineTo(adsorbPos.x, adsorbPos.y2 || ctx.canvas.height);
            }
            if (adsorbPos.y && adsorbPos.y != 0) {
                ctx.moveTo(adsorbPos.x1 || 0, adsorbPos.y)
                ctx.lineTo(adsorbPos.x2 || ctx.canvas.width, adsorbPos.y)
            }
            ctx.lineWidth = 1.2;
            ctx.strokeStyle = '#000';
            ctx.setLineDash([8, 12])
            ctx.stroke();
            ctx.restore()
        }
    }

    /**
     * 滚轮滚动事件,重绘网格
     * @param e 
     * @returns 
     */
    private mouseWheel(e: any) {
        e.preventDefault();
        if (!this.cbScale) return;
        const { x, y } = Utils.getMousePos(this.domElement, e);
        this.zoom(e.wheelDelta > 0 ? this.scaleSpeed : -this.scaleSpeed, { x, y })
        this.dispatch(new CustomEvent(Events.ZOOM, { detail: e }))
    };

    // 以鼠标中心点位置去放大
    private back2center(x: number, y: number, lastGirdSize: number) {
        const gridSize = this.getRatioSize(CoordinateSystem.GRID_SIZE);  // 当前单位大小
        const different = gridSize - lastGirdSize;   // 当前单位大小与上一次单位大小之差
        this.translate(-((x - this.pageSlicePos.x) / lastGirdSize) * different, -((y - this.pageSlicePos.y) / lastGirdSize) * different)
    }

    // --------------------以下是暴露的方法----------------------------
    // --------------------画布内元素的增删查API----------------------------
    drawFeatures(features: Feature[] = this.features) {
        Feature.Render = this;
        features.forEach(f => {
            if (!f.visible || f.pointArr.length <= 0) return;
            const pointArr = f.pointArr.map(p => this.getPixelPos(p, f.isFixedPos))
            if (!this.cbDrawMiniFeature && !f.cbDrawMiniFeature) {  // 是否渲染太小的元素，因为画布缩放的原因(也有元素本身很小)
                const [minX, maxX, minY, maxY] = Feature.getRectWrapExtent(f.pointArr, f.margin);
                if (Math.abs(maxX - minX) < GridSystem.scaleShowMinSize && Math.abs(maxY - minY) < GridSystem.scaleShowMinSize) return
            }
            if (!this.cbDrawOutScreen && !f.cbDrawOutScreen) { // 是否渲染屏幕外的元素
                if (!pointArr.some(p => p.x > 0 && p.x < this.ctx.canvas.width && p.y > 0 && p.y < this.ctx.canvas.height)) return
            }
            const lineWidth = this.getRatioSize(f.lineWidth, f.isPixelLen);
            const lineDashArr = f.lineDashArr.length == 2 ? [this.getRatioSize(f.lineDashArr[0], f.isPixelLen), this.getRatioSize(f.lineDashArr[1], f.isPixelLen)] : [];
            const path = f.draw(this.ctx, pointArr, lineWidth, lineDashArr as [number, number]);
            if (f.isOverflowHidden && path) {  // 子元素overflow裁剪
                this.ctx.save();
                f.isOverflowHidden && this.ctx.clip(path);
                this.ctx.restore();
            }
            if (Utils.isBasicFeature(f, false, false)) {   // 非基础元素不可再次渲染子元素
                this.drawFeatures(f.children);
            }
            // f.dispatch(new CustomEvent(Events.DRAW, { detail: '' }))   // 不要此事件,性能消耗比较大,凑合用画布的DRAW
        })
    }

    removeFeature(f: Feature | string | undefined | null) {
        if (!f) return;
        let feature: Feature | null | undefined = null;
        f instanceof Feature ? (feature = f) : feature = this.features.find(ff => ff.id == f)  // 找到目标元素
        if (feature) {
            if (feature.isDelWithChild) feature.children.forEach(cf => {
                if (feature instanceof Bbox) {
                    if (cf != feature.target) {   // bbox 删除时跳过目标元素
                        this.removeFeature(cf)
                    }
                } else {
                    this.removeFeature(cf)
                }
            })
            this.features = this.features.filter(f => feature && (f.id != feature.id));
            feature.dispatch(new CustomEvent(Events.DELETE, { detail: '' }))
            feature.destroy();
            feature = null;
        }
        return this;
    }
    
    addFeature(feature: Feature) {
        this.focusNode = feature;
        this.features.push(feature);
        if (Utils.isBasicFeature(feature) && feature.zIndex === 0) {
            const features = this.features.filter(f => Utils.isBasicFeature(f));  // 点状元素始终在最上层
            feature.zIndex = features.length;
        }
        this.features.sort((a, b) => a.zIndex - b.zIndex);
        feature.dispatch(new CustomEvent(Events.ADD, { detail: '' }))
        return this;
    }

    toCenter(target: Feature | GridSystem = this) { // 将元素或画布中心移动到canvas中心
        if (target instanceof Feature) {
            const { x, y } = this.getPixelPos(Feature.getCenterPos(target.pointArr));
            const { x: offsetX, y: offsetY } = this.getCenterDist({ x, y })
            this.translate(offsetX, offsetY);
        }
        if (target instanceof GridSystem) {
            this.translate(this.ctx.canvas.width / 2 - this.pageSlicePos.x, this.ctx.canvas.height / 2 - this.pageSlicePos.y)
        }
        return this;
    }

    getFocusNode(focusNode = this.focusNode): Feature | undefined {
        if (focusNode) {
            if (focusNode instanceof Bbox) {   // 如果是包围盒
                return focusNode.target;
            }
            if (focusNode instanceof Pnt) {  // 如果是控制点
                if (focusNode.parent instanceof Bbox) {   // bbox的ctrlNode
                    return focusNode.parent.target;
                } else if (focusNode.parent) {  // 比如线段的ctrlNode
                    return focusNode.parent as Feature;
                }
            }
            return focusNode as Feature;
        }
    }

    // --------------------------调整元素上下层级相关--------------------------------
    toDownIndex(feature: Feature) {  // 下移一层
        const index = this.features.findIndex(f => f === feature);
        Utils.swapElements<Feature>(this.features, index, index - 1);
        this.resortIndex();
        return this;
    }
    toUpIndex(feature: Feature) {
        const index = this.features.findIndex(f => f === feature);
        Utils.swapElements<Feature>(this.features, index, index + 1);
        this.resortIndex();
        return this;
    }
    toMinIndex(feature: Feature) {
        const index = this.features.findIndex(f => f === feature);
        this.features.splice(index, 1);
        this.features.unshift(feature);
        this.resortIndex();
        return this;
    }
    // 将元素置顶，在画布最上层显示
    toMaxIndex(feature: Feature) {
        const index = this.features.findIndex(f => f === feature);
        this.features.splice(index, 1);
        this.features.push(feature);
        this.resortIndex();
        return this;
    }
    resortIndex() {
        const features = this.features.filter(f => Utils.isBasicFeature(f, true, false));
        features.forEach((f, i) => f.zIndex = i);
        this.features.sort((a, b) => a.zIndex - b.zIndex);
        return this;
    }

    // ------------------ 获取像素，或相对坐标，宽度等-------------------------
    // 获取像素位置坐标
    getPixelPos(point: IRelativePos, isFixedPos = false): IPixelPos {
        if (isFixedPos) {
            return point
        } else {
            return {
                ...point,
                x: this.pageSlicePos.x + (point.x / CoordinateSystem.GRID_SIZE) * this.scale,
                y: this.pageSlicePos.y + (point.y / CoordinateSystem.GRID_SIZE) * this.scale,
            };
        }
    }

    // 获取相对位置坐标
    getRelativePos(point: IPixelPos, isFixedPos?: boolean): IRelativePos {
        if (isFixedPos) {
            return point
        } else {
            return {
                ...point,
                x: ((point.x - this.pageSlicePos.x) / this.scale) * CoordinateSystem.GRID_SIZE,
                y: ((point.y - this.pageSlicePos.y) / this.scale) * CoordinateSystem.GRID_SIZE,
            };
        }
    }
    // 获取像素长度， 比如获取元素的宽高
    getPixelLen(len: number, isPixelLen = false) {
        if (isPixelLen) {
            return len;
        } else {
            return len * CoordinateSystem.GRID_SIZE;
        }
    }
    getRelativeLen(len: number) {
        return len / CoordinateSystem.GRID_SIZE;
    }
    // 获取像素宽度， 比如lineWidth， fontSize, 随网格缩放而缩放
    getRatioSize(size: number, isPixelLen = false): number {
        if (isPixelLen) {
            return size;
        } else {
            return size * this.scale;
        }
    }

    // ------------------ 鼠标点击, 剪贴板, 拖拽方式去创建元素-----------------
    singleClick2Rect(rect: Rect | Circle) {
        this.cbOperable = false;
        const adsorbPnt = new AdsorbPnt(14);
        adsorbPnt.isPixelLen = true;
        const clear = () => {
            this.cbOperable = true;
            this.removeFeature(adsorbPnt);
            this.domElement.removeEventListener(Events.MOUSE_DOWN, click2draw)
            this.domElement.removeEventListener(Events.TOUCHSTART, click2draw)
            this.domElement.removeEventListener(Events.MOUSE_MOVE, move2draw)
        }
        const click2draw = (e: any) => {
            this.addFeature(rect);
            let clickPos: IRelativePos;
            if (GridSystem.isMobile) {
                clickPos = this.getRelativePos(Utils.getMousePos(this.domElement, e))
            } else {
                clickPos = Feature.getCenterPos(adsorbPnt.pointArr)
            }
            rect.setPos(clickPos.x, clickPos.y);
            clear(); // 单击一次即完成创建
        }
        const move2draw = () => {
            rect.setPos(adsorbPnt.position.x, adsorbPnt.position.y)
        }
        if (GridSystem.isMobile) this.domElement.addEventListener(Events.TOUCHSTART, click2draw)
        if (!GridSystem.isMobile) this.domElement.addEventListener(Events.MOUSE_DOWN, click2draw)
        if (!GridSystem.isMobile) {
            this.domElement.addEventListener(Events.MOUSE_MOVE, move2draw)
        }
        return clear;
    }
    continuousClick2Line(line: Line, fn?: Function) { // 鼠标点一下添加一个点去画折线
        this.cbOperable = false;
        const adsorbPnt = new AdsorbPnt(14);
        adsorbPnt.isPixelLen = true;
        const clear = () => {
            this.cbOperable = true;
            this.removeFeature(adsorbPnt);
            this.domElement.removeEventListener(Events.TOUCHSTART, click2draw)
            this.domElement.removeEventListener(Events.MOUSE_DOWN, click2draw)
            this.domElement.removeEventListener(Events.MOUSE_MOVE, move2draw)
            if (fn) fn();
        }
        const move2draw = () => {
            line.pointArr[line.pointArr.length - 1] = adsorbPnt.position;
        }
        const click2draw = (e: any) => {
            this.addFeature(line);
            let clickPos: IRelativePos;
            if (GridSystem.isMobile) {
                clickPos = this.getRelativePos(Utils.getMousePos(this.domElement, e))
                line.addPoint(clickPos, false);
            } else {
                if (e.buttons === Events.RIGHT_BTN) {   // 右键结束
                    clear();
                } else {  // 左键绘制
                    clickPos = Feature.getCenterPos(adsorbPnt.pointArr)
                    line.addPoint(clickPos, false);
                    if (line.pointArr.length == 1) {
                        line.addPoint(clickPos, false);
                    }
                }
            }
        }
        if (GridSystem.isMobile) this.domElement.addEventListener(Events.TOUCHSTART, click2draw)
        if (!GridSystem.isMobile) this.domElement.addEventListener(Events.MOUSE_DOWN, click2draw)
        if (!GridSystem.isMobile) {
            this.domElement.addEventListener(Events.MOUSE_MOVE, move2draw)
        }
        return clear;
    }
    continuousClick2CurveLine(line: Line) { // 鼠标点一下添加一个点去画折线
        this.cbOperable = false;
        line.getGeneratePointArrFn = (pointArr: IPoint[]) => {
            let curveNodes: IPoint[] = [] //整条曲线的点
            var cNodes = Utils.getCtrlNodes(pointArr, 0.45);
            Utils.optCNodes(cNodes);
            for (let i = 0; i < pointArr.length - 1; i++) {
                const curvePnts = Utils.getPntsOf3Bezier(pointArr[i], cNodes[i].Ai, cNodes[i + 1].Bi, pointArr[i + 1], 20).map((p, j) => ({ x: p.x, y: p.y, generated: j != 0 }))
                curveNodes = curveNodes.concat(curvePnts);
            }
            curveNodes.push(pointArr[pointArr.length - 1]);
            return curveNodes;
        }
        // line.ctrlTypes.splice(line.ctrlTypes.findIndex(ct => ct === CtrlType.BEZIER_CTRL), 1)
        var clickNodes: IPoint[] = []
        const adsorbPnt = new AdsorbPnt(14);
        adsorbPnt.isPixelLen = true;
        const clear = () => {
            this.cbOperable = true;
            this.removeFeature(adsorbPnt);
            this.domElement.removeEventListener(Events.TOUCHSTART, click2draw)
            this.domElement.removeEventListener(Events.MOUSE_DOWN, click2draw)
            this.domElement.removeEventListener(Events.MOUSE_MOVE, move2draw)
        }
        const move2draw = () => {
            clickNodes[clickNodes.length - 1] = adsorbPnt.position;
            if (line.getGeneratePointArrFn) {
                line.pointArr = line.getGeneratePointArrFn(clickNodes);
            }
        }
        const click2draw = (e: any) => {
            this.addFeature(line);
            let clickPos: IRelativePos;
            if (GridSystem.isMobile) {
                clickPos = this.getRelativePos(Utils.getMousePos(this.domElement, e))
                clickNodes.push(clickPos)
                if (line.getGeneratePointArrFn) {
                    line.pointArr = line.getGeneratePointArrFn(clickNodes);
                }
            } else {
                if (e.buttons === Events.RIGHT_BTN) {
                    clear();
                } else {
                    clickPos = Feature.getCenterPos(adsorbPnt.pointArr)
                    clickNodes.push(clickPos)
                    if (line.pointArr.length == 1) {
                        clickNodes.push(clickPos)
                    }
                }
            }
        }
        if (GridSystem.isMobile) this.domElement.addEventListener(Events.TOUCHSTART, click2draw)
        if (!GridSystem.isMobile) this.domElement.addEventListener(Events.MOUSE_DOWN, click2draw)
        if (!GridSystem.isMobile) this.domElement.addEventListener(Events.MOUSE_MOVE, move2draw)
        return clear;
    }
    downMove2Line(line: Line | Pen, fn?: Function) { // 鼠标按住不放持续画线
        this.cbOperable = false;
        let lastLineWidth = 0
        let lastTime = 0
        const clear = () => {
            this.cbOperable = true;
            this.domElement.removeEventListener(Events.TOUCHSTART, click2draw)
            this.domElement.removeEventListener(Events.MOUSE_DOWN, click2draw)
            this.domElement.removeEventListener(Events.MOUSE_MOVE, move2draw)
            this.domElement.removeEventListener(Events.MOUSE_UP, over2draw)
            document.removeEventListener(Events.TOUCHMOVE, move2draw)
            document.removeEventListener(Events.TOUCHEND, over2draw)
        }
        const move2draw = (e: any) => {
            const lastPnt = this.getRelativePos(Utils.getMousePos(this.domElement, e))
            line.addPoint(lastPnt);
            if (line.pointArr.length > 1 && line instanceof Pen) {
                const secondLastPnt = line.pointArr[line.pointArr.length - 2]
                // 自由画笔的宽度计算
                let lineWidth = 0
                const distance = Utils.getLenOfTwoPnts(lastPnt, secondLastPnt);
                const speed = distance / (Date.now() - lastTime) // 0.1 - 3  ,每一次移动的速度
                if (speed >= Pen.freeLineConfig.maxSpeed) {
                    lineWidth = Pen.freeLineConfig.minWidth
                } else if (speed <= Pen.freeLineConfig.minSpeed) {
                    lineWidth = Pen.freeLineConfig.maxWidth
                } else {
                    lineWidth = Pen.freeLineConfig.maxWidth - (speed / Pen.freeLineConfig.maxSpeed) * Pen.freeLineConfig.maxWidth
                }
                lineWidth = lineWidth * (1 / 3) + lastLineWidth * (2.3 / 3)
                lastLineWidth = lineWidth
                lastTime = Date.now();
                line.lineWidthArr.push(lineWidth);
            }
        }
        const over2draw = () => {
            if (fn) fn();
            clear();
        }
        const click2draw = (e: any) => {
            if (e.buttons === Events.LEFT_BTN || GridSystem.isMobile) {
                this.addFeature(line);
                const { x, y } = this.getRelativePos(Utils.getMousePos(this.domElement, e))
                line.addPoint({ x, y });
                if (GridSystem.isMobile) {
                    this.domElement.addEventListener(Events.TOUCHMOVE, move2draw, { passive: false })
                    this.domElement.addEventListener(Events.TOUCHEND, over2draw)
                } else {
                    this.domElement.addEventListener(Events.MOUSE_MOVE, move2draw)
                    this.domElement.addEventListener(Events.MOUSE_UP, over2draw)
                }
            } else {
                throw "请用左键绘制!"
            }
        }
        if (GridSystem.isMobile) this.domElement.addEventListener(Events.TOUCHSTART, click2draw)
        if (!GridSystem.isMobile) this.domElement.addEventListener(Events.MOUSE_DOWN, click2draw)
        return clear;
    }

    async clipboardToFeature(pos = Utils.getMousePos(this.domElement, this.mousePos)) { // 读取剪贴板内容生成文字或图片
        try {
            const clipboardData = await navigator.clipboard.read();
            pos = this.getRelativePos(pos)
            // 判断剪贴板数据类型为图像
            if (clipboardData) {
                const index = clipboardData[0].types.findIndex(type => type === 'image/png' || type === 'image/jpeg');
                if (index > -1) {
                    // 将图像转换成Blob对象
                    const imageBlob = new Blob([await clipboardData[0].getType(clipboardData[0].types[index])], { type: 'image/' + clipboardData[index].types[0].split('/')[1] });
                    const reader = new FileReader();
                    reader.readAsDataURL(imageBlob);  // 读取base64
                    reader.onload = () => {
                        const dataUrl = reader.result as string;
                        console.log(dataUrl, "dataUrl");
                        if (dataUrl) {
                            const img = new Img(dataUrl, pos.x, pos.y)
                            this.addFeature(img);
                        }
                    }
                    return;
                }
                // 判断剪贴板数据类型为文本
                if (clipboardData[0]?.types.includes('text/plain')) {
                    const textBlob = await clipboardData[0].getType(clipboardData[0].types[0]);
                    const reader = new FileReader();
                    reader.readAsText(textBlob);  // 获取文本
                    reader.onload = () => {
                        const txt = reader.result as string
                        if (txt && txt.length > 0) {
                            const text = new Text(txt, pos.x, pos.y, this.ctx.measureText(txt).width)
                            this.addFeature(text);
                        }
                    }
                    return;
                }
            }
        } catch (error) {
            console.error('Failed to read clipboard content: ', error);
            return null;
        }
    }
    dropToFeature(e: any) { // 拖放去添加图片
        //取得拖进来的文件
        const data = e.dataTransfer;
        const files = data.files;  // file继承与blob
        if (files && (files[0].type === 'image/png' || files[0].type === 'image/jpeg' || files[0].type === 'video/mp4')) {
            const pos = this.getRelativePos(Utils.getMousePos(this.domElement, { x: e.clientX, y: e.clientY }))
            const reader = new FileReader();
            reader.readAsDataURL(files[0]);  // base64
            reader.onload = () => {
                const dataUrl = reader.result as string;
                if (dataUrl) {
                    const img = new Img(dataUrl, pos.x, pos.y)
                    this.addFeature(img);
                }
            }
        }
    }

    // ---------------------bbox, 区域选择
    enableBbox(feature: Feature | null | undefined = null) {  // 包围盒控制点
        let bbox = this.features.find(f => f instanceof Bbox);
        this.removeFeature(bbox)
        if (this.cbTransformFeature && feature && feature.cbTransform && (Utils.isBasicFeature(feature) || feature instanceof SelectArea) && this.cbOperable) bbox = new Bbox(feature);
        return bbox;
    }
    enableSelectArea(enable = true) {   // 区域选择
        let sa = this.features.find(f => f instanceof SelectArea);
        this.removeFeature(sa);
        if (enable && this.cbOperable) SelectArea.getInstance(true);
        return sa;
    }

    // -------------------保存画布状态,读取画布状态,加载状态---------------------------
    save() {
        const props = Object.entries(this);
        const record: Partial<GridSystem> = {}
        props.forEach(p => {
            const propName = p[0]
            const value = p[1];
            if (!['domElement', 'ctx', 'features', 'focusNode', 'hoverNode'].includes(propName) && !propName.startsWith("_")) {
                record[propName] = value
            }
        })
        record.features = this.features.filter(f => Utils.isBasicFeature(f)).map(f => this.recordFeature(f)) as unknown as Feature[]
        const recordStr = JSON.stringify(record);
        localStorage.setItem("record", recordStr);
        return recordStr
    }
    loadData(recordStr?: string) {  // 加载数据
        if (!recordStr) { recordStr = localStorage.getItem("record") as string };
        const props = JSON.parse(recordStr) as Partial<GridSystem>;
        for (const propName in props) {
            if (props[propName] != undefined && this.hasOwnProperty(propName) && !['domElement', 'ctx', 'features', 'focusNode', 'extent'].includes(propName)) {
                this[propName] = props[propName];
            }
            Feature.Render = this;
            if (propName === 'features') {
                const features = props.features as unknown as IProps[];
                [...features].reverse().forEach(f => {
                    this.createFeature(f)
                })
            }
        }
        return this;
    }
    loadFont(fontFamily: FontFamily | string, url?: string) { // 加载字体
        return new Promise((resolve, reject) => {
            if (!url) { url = `url(${fontMap.get(fontFamily as FontFamily)})` }
            const fontface = new FontFace(fontFamily, url);
            if (!document.fonts.has(fontface)) {
                fontface.load().then(function (loadFace) {
                    console.log("字体加载完毕!");
                    document.fonts.add(loadFace);
                    resolve(true)
                }).catch(e => {
                    reject(false)
                })
            }
        })
    }

    // -------------------创建feature, 修改feature属性, 读取feature属性---------------------------
    createFeature(props: IProps, isChild = false): Feature | undefined {
        let feature: Feature | undefined;
        const f = this.features.find(f => f.id === props.id)  // 如果已存在,直接返回
        if (f) return f;
        const children = props.children.map(cd => this.createFeature(cd, true)) as Feature[];    // 先创建子元素
        // props = Object.assign({}, props, newProps);
        switch (props.className) {
            case ClassName.VIDEO:
                feature = new Video(props.src || '', props.position?.x, props.position?.y, props.size?.width, props.size?.height)
                break;
            case ClassName.IMG:
                feature = new Img(props.src || '', props.position?.x, props.position?.y, props.size?.width, props.size?.height);
                break;
            case ClassName.RECT:
                feature = new Rect(props.position?.x, props.position?.y, props.size?.width, props.size?.height)
                break;
            case ClassName.TEXT:
                feature = new Text(props.content, props.position?.x, props.position?.y, props.size?.width, props.size?.height)
                break;
            case ClassName.CIRCLE:
                feature = new Circle(props.position?.x, props.position?.y, props.size?.width, props.size?.height)
                break;
            case ClassName.GROUP:
                feature = new Group()
                break;
            case ClassName.FEATURE:
                feature = new Feature(props.pointArr)
                break;
            case ClassName.LINE:
                feature = new Line(props.pointArr)
                break;
            case ClassName.PEN:
                feature = new Pen(props.pointArr)
                break;
            case ClassName.LINK:
                if (props.startFeature && props.endFeature) {
                    let startFeature, endFeature;
                    if (props.startFeature.id && props.endFeature.id) {  // 是元素
                        startFeature = this.features.find(f => f.id === (props.startFeature && props.startFeature.id));
                        endFeature = this.features.find(f => f.id === (props.endFeature && props.endFeature.id));
                    } else {  // 是点坐标
                        startFeature = props.startFeature;
                        endFeature = props.endFeature;
                    }
                    // console.log(startFeature, endFeature);
                    if (startFeature && endFeature) {
                        feature = new Link(startFeature, endFeature)
                    } else {
                        throw "参数异常!"
                    }
                } else {
                    throw "参数异常!"
                }
                break;
            default:
                break;
        }
        if (feature) {
            this.setTarget(feature, props);
            children.forEach(cd => feature && feature.addChild(cd, false))  // 不需要重复位移
            if (!isChild) this.addFeature(feature);
        }
        return feature;
    }

    setTarget(feature: Feature, props: IProps) {
        feature.children = [];
        props.children.forEach(cdProps => {
            const child = this.createFeature(cdProps, true)
            if (child) feature.addChild(child, false)
        })
        for (const key in props) {
            if (props[key] != undefined && feature.hasOwnProperty(key) &&
                !['gls', 'parent', 'domElement', "group", 'startFeature', 'endFeature', 'children'].includes(key)) {
                feature[key] = props[key];
            }
        }
        return this;
    }

    recordFeature(feature: Feature | IProps, onlyStyle = false): Partial<IProps> | void {  // 复制或读取元素属性
        const props = Object.entries(feature);
        const record: Partial<IProps> = {}
        props.forEach(p => {
            const propName = p[0]
            const value = p[1]
            if (['gls', 'parent', 'domElement', "group"].includes(propName) || propName.startsWith("_")) {
                return;
            }
            if (['children'].includes(propName)) {
                const features = value as Feature[];
                record[propName] = features.filter(f => Utils.isBasicFeature(f)).map(child => this.recordFeature(child))
                return;
            }
            if (['startFeature', 'endFeature'].includes(propName)) {
                record[propName] = this.recordFeature(value)
                return;
            }
            if (typeof value === 'object') {
                record[propName] = JSON.parse(JSON.stringify(value))
                return;
            }
            record[propName] = value
        })
        return record;
    }

    // ----------------------剪切板相关---------------------------
    copyImageToClipboard(feature = this.getFocusNode(), padding = 10): Promise<Blob> { // 复制元素为png到剪贴板
        return new Promise((resolve, reject) => {
            if (feature) {
                const offscreenCanvas = document.createElement('canvas');
                const ctx = offscreenCanvas.getContext('2d') as CanvasRenderingContext2D;
                const pointArr = feature.pointArr.map(p => this.getPixelPos(p, feature.isFixedPos))
                const [leftTop, rightTop, rightBottom, leftBottom] = Feature.getRectWrapPoints(pointArr, feature.margin);
                const lineWidth = this.getRatioSize(feature.lineWidth);
                offscreenCanvas.width = Math.abs(rightTop.x - leftTop.x) + padding;
                offscreenCanvas.height = Math.abs(leftTop.y - leftBottom.y) + padding;
                // 将多边形移动到Canvas的左上角 
                pointArr.forEach(point => {
                    point.x -= leftTop.x - padding / 2;  // 水平方向移动到左侧边界
                    point.y -= leftTop.y - padding / 2; // 垂直方向移动到顶部边界  
                });
                ctx.fillStyle = this.background
                ctx.fillRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);
                if (Utils.isBasicFeature(feature)) {
                    const lineDashArr = feature.lineDashArr.length == 2 ? [this.getRatioSize(feature.lineDashArr[0], feature.isPixelLen), this.getRatioSize(feature.lineDashArr[1], feature.isPixelLen)] : [];
                    feature.draw(ctx, pointArr, lineWidth, lineDashArr);
                }
                this.drawFeatures2ctx(ctx, [feature], { x: leftTop.x - padding / 2, y: leftTop.y - padding / 2 });
                const url = offscreenCanvas.toDataURL("image/png");   // canvas 转 图片
                fetch(url).then(data => {
                    data.blob().then(blob => { // 图片转blob
                        const data = [new ClipboardItem({
                            [blob.type]: blob
                        })];
                        navigator.clipboard.write(data).then(() => {
                            console.log("复制成功!");
                            // resolve(url)
                        }, (err) => {
                            reject("复制失败:" + err)
                        })
                    })
                });
            }
        })
    }
    copySvgToClipboard(feature = this.getFocusNode(), padding = 10, background = this.background): Promise<string> {// 复制元素为svg到剪贴板
        let svgstr = '';
        // 绘制子元素,子元素偏移的距离等于父元素偏移的距离  递归,道理跟刚才一样
        const addChildrenSvg = (features: Feature[], offset: IPoint, padding = 0) => {
            features.forEach(cf => {
                const pointArr = cf.pointArr.map(p => this.getPixelPos(p, cf.isFixedPos))
                // 将多边形移动到Canvas的左上角  
                pointArr.forEach(point => {
                    point.x -= offset.x;  // 水平方向移动到左侧边界
                    point.y -= offset.y; // 垂直方向移动到顶部边界  
                });
                const lineWidth = this.getRatioSize(cf.lineWidth);
                svgstr += cf.getSvg(pointArr, lineWidth);   // svg旋转默认围绕viewBox左上角
                addChildrenSvg(cf.getBasicChilds(), offset, padding)
            })
        }
        return new Promise((resolve, reject) => {
            if (feature) {
                const pointArr = feature.pointArr.map(p => this.getPixelPos(p, feature.isFixedPos))
                const [leftTop, rightTop, rightBottom, leftBottom] = Feature.getRectWrapPoints(pointArr, feature.margin);
                const width = Math.abs(rightTop.x - leftTop.x) + padding;
                const height = Math.abs(leftTop.y - leftBottom.y) + padding;
                const lineWidth = this.getRatioSize(feature.lineWidth);
                // 将多边形移动到SVG的左上角
                pointArr.forEach(point => {
                    point.x -= leftTop.x - padding / 2;  // 水平方向移动到左侧边界
                    point.y -= leftTop.y - padding / 2; // 垂直方向移动到顶部边界 
                });
                svgstr += feature.getSvg(pointArr, lineWidth);   // svg旋转默认围绕viewBox左上角
                addChildrenSvg(feature.getBasicChilds(), { x: leftTop.x - padding / 2, y: leftTop.y - padding / 2 });
                const svgStr = `<svg version="1.1" 
                      xmlns="http://www.w3.org/2000/svg" 
                      viewBox="0 0 ${width} ${height}"
                      width="${width}"
                      height="${height}"
                      style="background-color: ${background};
                  ">${svgstr}</svg>`
                Utils.clipboardText(svgStr);
                // // 使用剪切板API进行复制
                // const blob = new Blob([svgStr], { type: 'text/plain' });
                // const data = [new ClipboardItem({
                //     [blob.type]: blob
                // })];
                // navigator.clipboard.write(data).then(() => {
                //     console.log("复制成功!");
                resolve(svgStr)
                // }, (err) => {
                //     reject("复制失败:" + err)
                // })
            }
        })
    }

    // ----------------------------画布相关操作方法------------------------------
    translate(offsetX: number = 0, offsetY: number = 0, isout?: { isoutX: boolean, isoutY: boolean }) {  // 移动画布
        const [leftTop, rightTop, rightBottom, leftBottom] = this.getFeaturesRange();
        if (!this.cbContentTranslateOutScreen && isout) {
            if (leftTop.x < 0 && offsetX >= 0 && isout.isoutX) offsetX = 0;
            if (leftTop.x < 0 && offsetX <= 0) offsetX = 0

            if (rightTop.y < 0 && offsetY >= 0 && isout.isoutY) offsetY = 0;
            if (rightTop.y < 0 && offsetY <= 0) offsetY = 0

            if (rightBottom.x > this.ctx.canvas.width && offsetX <= 0 && isout.isoutX) offsetX = 0;
            if (rightBottom.x > this.ctx.canvas.width && offsetX >= 0) offsetX = 0

            if (leftBottom.y > this.ctx.canvas.height && offsetY <= 0 && isout.isoutY) offsetY = 0
            if (leftBottom.y > this.ctx.canvas.height && offsetY >= 0) offsetY = 0
        }
        this.pageSlicePos.x += offsetX;
        this.pageSlicePos.y += offsetY;
        this.dispatch(new CustomEvent(Events.TRANSLATE, { detail: this.pageSlicePos }))
        return this;
    }
    zoom(factor: number, point: IPixelPos = this.getCenterPos()) { // 缩放多少 +0.2  -0.2
        const { x, y } = point;
        const lastGirdSize = this.getRatioSize(CoordinateSystem.GRID_SIZE);  // 上一次的gridSize大小
        const nextScale = this.scale + factor
        if (factor > 0) {
            if (nextScale > this.maxScaleSize) { // 如果是放大，并且超过最大放大倍数，则直接放大到最大倍数
                this.scale = this.maxScaleSize
            } else {
                this.scale = nextScale;
                this.back2center(x, y, lastGirdSize);
            }
        } else {
            if (nextScale < this.minScaleSize) {
                this.scale = this.minScaleSize
            } else {// 在范围内，就直接放大
                this.scale = nextScale;
                this.back2center(x, y, lastGirdSize);
            }
        }
        return this;
    }
    zoomTo(factor: number, point: IPixelPos = this.getCenterPos()) { // 缩放至指定大小
        this.zoom(factor - this.scale, point)
        return this;
    }
    getCenterPos(): IPixelPos { // 获取中心点
        return { x: this.domElement.width / 2, y: this.domElement.height / 2 }
    }
    setSize(width?: number | null, height?: number | null) {  // 设置画布大小
        if (width) this.ctx.canvas.width = width;
        if (height) this.ctx.canvas.height = height;
        this.draw(false)
        return this;
    }
    getFeaturesRange(features = this.features): IPixelPos[] {  // 获取多个元素的包围盒矩形的四个坐标点
        const featuresPointArr: IRelativePos[] = []
        features.forEach(f => featuresPointArr.push(...Feature.getRectWrapPoints(f.pointArr, f.margin)));
        return Feature.getRectWrapPoints(featuresPointArr.map(p => this.getPixelPos(p)));  // [leftTop, rightTop, rightBottom, leftBottom]
    }
    getCenterDist(point: IPixelPos) { // 求点与canvas中心的距离
        const canvasCenter = this.getCenterPos();
        return {
            x: canvasCenter.x - point.x,
            y: canvasCenter.y - point.y
        }
    }
    /**
  * 居中,并缩放至所有元素都在canvas范围内
  * @param padding 上下或左右的边距
  */
    toFitView(features: Feature[] = this.features, padding: number = 20, isCenter = true) {
        features = features.filter(f => Utils.isBasicFeature(f));
        if (features.length <= 0) return;
        const domElement = this.ctx.canvas;
        // 先缩放
        const [leftTop, rightTop, rightBottom, leftBottom] = this.getFeaturesRange(features);   // 所有元素的范围大小
        const totalHeight = Math.abs(leftBottom.y - leftTop.y);
        const totalWidth = Math.abs(rightTop.x - leftTop.x);
        if (totalWidth > totalHeight) {
            this.scale = domElement.width / ((totalWidth + padding) / this.scale);   // 像素宽度/scale是相对宽度, 画布宽度/相对宽度得到缩放比例 这个跟miniMap算法一样
        } else {
            this.scale = domElement.height / ((totalHeight + padding) / this.scale);
        }
        if (isCenter) {
            setTimeout(() => {
                // 后居中
                const [leftTop1, rightTop1, rightBottom1, leftBottom1] = this.getFeaturesRange(features);
                const { x: distX, y: distY } = this.getCenterDist({ x: (rightTop1.x + leftBottom1.x) / 2, y: (leftTop1.y + rightBottom1.y) / 2 });
                this.pageSlicePos.x = this.pageSlicePos.x + distX
                this.pageSlicePos.y = this.pageSlicePos.y + distY  // 以所有元素的中心点对齐
            }, 100)
        }
        return this;
    }

    // 绘制子元素,子元素偏移的距离等于父元素偏移的距离
    drawFeatures2ctx (ctx: CanvasRenderingContext2D, features: Feature[], offset: IPoint) {
        features.forEach(cf => {
            const pointArr = cf.pointArr.map(p => this.getPixelPos(p, cf.isFixedPos))
            // 将多边形移动到Canvas的左上角  
            pointArr.forEach(point => {
                point.x -= offset.x;  // 水平方向移动到左侧边界
                point.y -= offset.y; // 垂直方向移动到顶部边界  
            });
            const lineWidth = this.getRatioSize(cf.lineWidth);
            const lineDashArr = cf.lineDashArr.length == 2 ? [this.getRatioSize(cf.lineDashArr[0], cf.isPixelLen), this.getRatioSize(cf.lineDashArr[1], cf.isPixelLen)] : [];
            cf.draw(ctx, pointArr, lineWidth, lineDashArr);
            this.drawFeatures2ctx(ctx, cf.getBasicChilds(), offset)
        });
        return this;
    }

    /**
     *  整个画布导出为图片URL
     * @param isFitView 
     * @param padding 
     * @param zoom 
     * @returns 
     */
    toImage(isFitView = false, background = this.background, padding = 100, zoom = 20) {
        padding = padding * 2
        if (isFitView) {
            const features = this.features.filter(f => Utils.isBasicFeature(f, true, true, true))
            const scale = this.scale;
            this.scale = zoom;  // 放大倍数,数值越大图片越清晰,同时文件也越大
            const [leftTop, rightTop, rightBottom, leftBottom] = this.getFeaturesRange(this.features);   // 所有元素的范围大小
            const totalWidth = Math.abs(rightTop.x - leftBottom.x);
            const totalHeight = Math.abs(rightBottom.y - leftTop.y);
            const offscreenCanvas = document.createElement('canvas');
            offscreenCanvas.width = totalWidth + padding;
            offscreenCanvas.height = totalHeight + padding;
            const ctx = offscreenCanvas.getContext('2d') as CanvasRenderingContext2D;
            ctx.fillStyle = background
            ctx.fillRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);
            this.drawFeatures2ctx(ctx, features, { x: leftTop.x - padding / 2, y: leftTop.y - padding / 2 });
            this.scale = scale;
            return offscreenCanvas.toDataURL("image/png");
        } else {
            return this.domElement.toDataURL("image/png");
        }
    }

    // ------------------------网格坐标相关方法--------------------------
    // 根据相对坐标获取网格坐标
    getGridPosByRelativePos(point: IRelativePos): IGridPos {
        if (point.x === 0 || point.y === 0) throw "x或y不能为0!"
        const gridSize = Utils.getUnitSize();  // 实际网格单元大小
        const gx = point.x > 0 ? Math.ceil(point.x / gridSize) : Math.floor(point.x / gridSize);
        const gy = point.y > 0 ? Math.ceil(point.y / gridSize) : Math.floor(point.y / gridSize);
        return { x: gx, y: gy }
    }
    // 根据网格坐标获取相对坐标
    getRelativePosByGridPos(point: IGridPos): IRelativePos {
        if (point.x === 0 || point.y === 0) throw "x或y不能为0!"
        const gridSize = Utils.getUnitSize();  // 实际网格单元大小
        return {
            x: point.x > 0 ? gridSize * (point.x - 1) + gridSize / 2 : gridSize * point.x + gridSize / 2,
            y: point.y > 0 ? gridSize * (point.y - 1) + gridSize / 2 : gridSize * point.y + gridSize / 2,
        }
    }

    // ----------------------其他功能性API------------------------
    /**
 * 根据一个点获取他周围的吸附距离
 * @param point 
 * @returns 
 */
    getAdsorb2Grid(point: IRelativePos) {
        if (!this.cbAdsorption) return { x: 0, y: 0 };
        const gridSize = CoordinateSystem.GRID_SIZE;
        let offsetX = 0, offsetY = 0;
        // 相对像素
        // 吸附的约束，灵敏度
        const min = gridSize * .2; // 靠左的距离
        const max = gridSize - gridSize * .2;  // 靠右的距离
        //  ------------- 水平对齐
        const diffX = getDeviation(point.x);
        if (offsetX == 0 && (diffX > 0 && diffX < min) || (diffX < 0 && diffX > -min)) {
            offsetX = -point.x % (gridSize * gridSize);
        }
        if (offsetX == 0 && (diffX > max && diffX < gridSize) || (diffX > -gridSize && diffX < -max)) {
            offsetX = (gridSize * gridSize) * (diffX > 0 ? 1 : -1) - point.x % (gridSize * gridSize);
        }
        //  ------------- 垂直对齐
        const diffY = getDeviation(point.y);
        if (offsetY == 0 && (diffY > 0 && diffY < min) || (diffY < 0 && diffY > -min)) {
            offsetY = -point.y % (gridSize * gridSize);
        }
        if (offsetY == 0 && (diffY > max && diffY < gridSize) || (diffY > -gridSize && diffY < -max)) {
            offsetY = (gridSize * gridSize) * (diffY > 0 ? 1 : -1) - point.y % (gridSize * gridSize);
        }

        return { x: offsetX, y: offsetY };

        function getDeviation(num: number): number {
            const gridSize = CoordinateSystem.GRID_SIZE;
            return (num / gridSize) % gridSize;
        }
    }

    getAdsorb2Point(point: IRelativePos) {
        if (!this.cbAdsorption) return { x: 0, y: 0 };
        // 吸附的约束，灵敏度
        const min = 3;
        const points = this.features.filter(f => f instanceof Pnt && f.position.x != point.x && f.position.y != point.y).map(f => Feature.getCenterPos(f.pointArr));
        const nearP = Utils.getNearestPoint(point, points) as IRelativePos;
        if (nearP) {
            const d = Utils.getLenOfTwoPnts(nearP, point);
            if (d < min) {
                return { x: nearP.x - point.x, y: nearP.y - point.y };
            }
        }
        return { x: 0, y: 0 };
    }

    destroy() {
        cancelAnimationFrame(timerOfDraw);
        this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
        // ... 移出事件
        this.features.forEach(f => {
            this.removeFeature(f);
        })
    }

    on(type: string, callback: Function) {
        if (!this._listeners[type]) {
            this._listeners[type] = [];
        }
        this._listeners[type].push(callback);
    }
    // 取消订阅事件
    off(type: string, callback: Function) {
        if (!this._listeners[type]) return;
        const index = this._listeners[type].indexOf(callback);
        if (index != -1) {
            this._listeners[type].splice(index, 1);
        }
    }
    // 触发事件
    dispatch(event: CustomEvent) {
        if (this._listeners[event.type]) {
            this._listeners[event.type].forEach(callback => {
                callback(event);
            });
        }
    }

}

export default GridSystem;